<?php
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
/**
 * PHP version 5
 *
 * This source file is subject to version 2.1 of the GNU Lesser General Public
 * License, that is bundled with this package in the file COPYING, available
 * through the world wide web at the following URI:
 * http://www.gnu.org/copyleft/lesser.html.
 *
 * @package    ZNF
 * @subpackage Action
 * @author     Alessandro Rossini <http://www.alessandrorossini.org>
 * @author     Graziano Liberati <http://www.liberati.org>
 * @author     Tomasz Kuter <evolic@interia.pl>
 * @copyright  2004-2007 The ZNF Development Team
 * @license    LGPL License 2.1 <http://www.gnu.org/copyleft/lesser.html>
 * @version    SVN $Id: RequestProcessor.php 44 2007-07-22 23:55:30Z evolic $
 * @since      Release 0.5.0
 * @link       http://www.zeronotice.org
 */

require_once('ZNF/Action/Action.php');
require_once('ZNF/Action/ActionErrors.php');
require_once('ZNF/Action/ActionForm.php');
require_once('ZNF/Action/ActionForward.php');
require_once('ZNF/Action/ActionMapping.php');

/**
 * <i>ZNF_Action_RequestProcessor</i> contains the processing logic flow.
 *
 * Subclasses can customize the request processing behavior overriding the
 * methods for which they wish to provide modified functionality.
 *
 * @access     public
 * @package    ZNF
 * @subpackage Action
 * @author     Alessandro Rossini <http://www.alessandrorossini.org>
 * @author     Graziano Liberati <http://www.liberati.org>
 * @author     Tomasz Kuter <evolic@interia.pl>
 * @copyright  2004-2007 The ZNF Development Team
 * @license    LGPL License 2.1 <http://www.gnu.org/copyleft/lesser.html>
 * @version    SVN $Id: RequestProcessor.php 44 2007-07-22 23:55:30Z evolic $
 * @since      Release 0.5.0
 * @link       http://www.zeronotice.org
 */
class ZNF_Action_RequestProcessor
{

    /**
     * The configuration of the module in the application configuration file.
     *
     * @access protected
     * @var array $_appModuleConfig
     */
    protected $_appModuleConfig;

    /**
     * Constructs a new <i>ZNF_Action_RequestProcessor</i> object.
     *
     * @access public
     * @param String $module Name of the module requested
     */
    public function __construct($module)
    {
        $appConfig = ZNF_Config_AppConfig::getInstance();

        // gets the app module config
        $this->_appModuleConfig = $appConfig->findModule($module);
    }

    /**
     * Instruction executed before the execution of the process method.
     *
     * @access public
     * @return boolean
     */
    public function preProcess()
    {
        return true;
    }

    /**
     * Instruction executed after the execution of the process method.
     *
     * @access public
     * @return boolean
     */
    public function postProcess()
    {
        return true;
    }

    /**
     * Processes the action requested with the default processing flow.
     *
     * @access public
     * @param string $path
     * @return boolean
     */
    public function process($path)
    {
        // constructs the mapping object
        $mapping = $this->_processMapping($path);

        // validates the user authorization
        if (!$this->_processRoles($mapping)) {
            return false;
        }

        // constructs the form object
        $form = $this->_processActionForm($mapping);

        // populates the form object with the input data
        $this->_processPopulate($form);

        // validates the input data if requested by the configuration file
        if (!$this->_processValidate($form, $mapping)) {
            return false;
        }

        // checks if in the configuration file is specified to forward to another action
        if ($this->_processForward($mapping)) {
            return true;
        }

        // constructs the action object
        $action = $this->_processActionCreate($mapping);

        // performs the action by the execute method of the action class
        $forward = $this->_processActionPerform($action, $form, $mapping);

        // finds forward by the result of the action
        $this->_processForwardConfig($forward, $form, $mapping);

        // end of the processing logic flow
        return true;
    }

    /**
     * Extracts the mapping of the action requested from the configuration file.
     *
     * @access protected
     * @param string $path
     * @return ZNF_Action_ActionMapping
     */
    protected function _processMapping($path)
    {
        // gets the module configuration
        $moduleConfig = ZNF_Config_ModulesConfig::getInstance($this->_appModuleConfig);

        $actionConfig = $moduleConfig->findActionConfig($path);
        $globalForwardsConfig = $moduleConfig->getGlobalForwardsConfig();

        if ($actionConfig) {
            $mapping = new ZNF_Action_ActionMapping($actionConfig, $globalForwardsConfig);
        } else {
            $mapping = new ZNF_Action_ActionMapping($moduleConfig->findActionConfig('default'), $globalForwardsConfig);
        }

        return $mapping;
    }

    /**
     * Validates the authorization of the user to access to the action requested.
     *
     * @access protected
     * @param ZNF_Action_ActionMapping $mapping
     * @return boolean
     */
    protected function _processRoles($mapping)
    {
        // gets roles
        $roles = $mapping->getRoles();

        if (!$roles) {
            // for this action is not required a role
            return true;
        }

        if(isset($_SESSION['znf']['roles'])) {
            $rolesArray = explode(',', $roles);

            foreach ($_SESSION['znf']['roles'] as $current) {

                if (in_array($current, $rolesArray)) {
                    // the user has a role to execute this action
                    return true;
                }
            }
        }

        // there are no default roles associated to anonymous user or the roles
        // are not matched, authentication is required
        if ($_SESSION['znf']['authenticated'] == false) {
            // force to recreateURL after successfull login
            $_SESSION['znf']['recreateURL'] = true;
            $_SESSION['znf']['requestURL'] = $_SERVER['REQUEST_URI'];

            $appConfig = ZNF_Config_AppConfig::getInstance();

            // gets the authentication configuration
            $authConfig = $appConfig->findAuthConfig();

            if (ereg('//', $authConfig['path'])) {
                // authentication path is set to an action of another module

                // extract module and action name
                $moduleAction = explode('//', $authConfig['path']);

                $znf = new ZNF();

                // update module and action in the request
                $znf->updateModule($moduleAction[0]);
                $znf->updateAction($moduleAction[1]);

                $znf->process();

            } else if (ereg('/', $authConfig['path'])) {
                // authentication path is set to a view

                $authConfig['path'] = ZNF::MODULES_DIR . '/' . $authConfig['path'];

                if (!file_exists($authConfig['path'])) {
                    $translation = new ZNF_Presentation_Translation('ZNF', $_SESSION['znf']['lang']);
                    throw new ZNF_Action_RequestProcessorException($translation->get('errorLoadView'));
                }

                require_once($authConfig['path']);
            } else {
                // authentication path is set to an action
                $this->process($authConfig['path']);
            }

            return false;
        } else {
            $appConfig = ZNF_Config_AppConfig::getInstance();

            // gets the wrong authentication configuration
            $wrongAuthConfig = $appConfig->findWrongAuthConfig();

            if (ereg('//', $wrongAuthConfig['path'])) {
                // wrong authentication path is set to an action of another module

                // extract module and action name
                $moduleAction = explode('//', $wrongAuthConfig['path']);

                $znf = new ZNF();

                // update module and action in the request
                $znf->updateModule($moduleAction[0]);
                $znf->updateAction($moduleAction[1]);

                $znf->process();

            } else if (ereg('/', $wrongAuthConfig['path'])) {
                // authentication path is set to a view
                $wrongAuthConfig['path'] = ZNF::MODULES_DIR . '/' . $wrongAuthConfig['path'];

                if (!file_exists($wrongAuthConfig['path'])) {
                    $translation = new ZNF_Presentation_Translation('ZNF', $_SESSION['znf']['lang']);
                    throw new ZNF_Action_RequestProcessorException($translation->get('errorLoadView'));
                }

                require_once($wrongAuthConfig['path']);
            } else {
                // wrong authentication path is set to an action
                $this->process($wrongAuthConfig['path']);
            }

            return false;
        }

        // the user is authenticated and he has not a role to execute this
        // action
        $translation = new ZNF_Presentation_Translation('ZNF', $_SESSION['znf']['lang']);
        throw new ZNF_Action_RequestProcessorException($translation->get('errorAuthorizationRequired'));
    }

    /**
     * If the <i>name</i> property of the <i>ZNF_Action_ActionMapping</i> is found constructs a <i>ZNF_Action_ActionForm</i> object to process the input data.
     *
     * If the package of which the <i>ZNF_Action_ActionForm</i> class belongs is
     * not found a <i>ZNF_Action_RequestProcessorException</i> is raised.
     *
     * @access protected
     * @param ZNF_Action_ActionMapping $mapping
     * @return ZNF_Action_ActionForm
     */
    protected function _processActionForm($mapping)
    {
        // gets the name of the form
        $name = $mapping->getName();

        if (!$name) {
            // no form name found
            return null;
        }

        // gets the module configuration
        $moduleConfig = ZNF_Config_ModulesConfig::getInstance($this->_appModuleConfig);

        // extracts the configuration of the form
        $formBeanConfig = $moduleConfig->findFormBeanConfig($name);

        if (!$formBeanConfig) {
            // no form configuration found
            return null;
        }

        $form = null;
        $scope = $mapping->getScope();

        // try to load a pre-existing form from the request or session
        if ($scope == 'request') {
            if (is_object($_REQUEST['znf']['form'])) {
                $form = $_REQUEST['znf']['form'];
            }
        } elseif ($scope == 'session') {
            if (is_object($_SESSION['znf']['form'])) {
                $form = $_SESSION['znf']['form'];
            }
        }

        if (is_object($form)) {
            return $form;
        }

        // constructs the ZNF_Action_ActionForm object
        $type = $formBeanConfig['type'];
        $file = ZNF::MODULES_DIR . '/' . preg_replace('/_/', '/', $type) . '.php';

        if (!file_exists($file)) {
            $translation = new ZNF_Presentation_Translation('ZNF', $_SESSION['znf']['lang']);
            throw new ZNF_Action_RequestProcessorException($translation->get('errorLoadActionForm'));
        }

        // requires the file of the of the ZNF_Action_ActionForm
        require_once($file);
        $form = new $type();

        if ($scope == 'request') {
            $_REQUEST['znf']['form'] = $form;
        } elseif ($scope == 'session') {
            $_SESSION['znf']['form'] = $form;
        }

        return $form;
    }

    /**
     * Populates internal properties of the <i>ZNF_Action_ActionForm</i> object with the input data.
     *
     * @access protected
     * @param ZNF_Action_ActionForm $form
     * @return boolean
     */
    protected function _processPopulate($form)
    {
        if (!$form) {
            // form not set
            return true;
        }

        return $form->populate();
    }

    /**
     * Validates input data.
     *
     * @access protected
     * @param ZNF_Action_ActionForm $form
     * @param ZNF_Action_ActionMapping $mapping
     * @return boolean
     */
    protected function _processValidate($form, $mapping)
    {
        if ($form && $mapping->getValidate() == 'true') {

            // validates input data
            $errors = $form->validate();

            if ($errors->getNumErrors()) {

                $_REQUEST['errors'] = $errors;
                $input = $mapping->getInput();

                if (ereg('//', $input)) {
                    // forward is set to an action of another module

                    // extract module and action name
                    $moduleAction = explode('//', $input);

                    $znf = new ZNF();

                    // update module and action in the request
                    $znf->updateModule($moduleAction[0]);
                    $znf->updateAction($moduleAction[1]);

                    $znf->process();

                } else if (ereg('/', $input)) {
                    // forward is set to a view

                    $input = ZNF::MODULES_DIR . '/' . $input;

                    if (!file_exists($input)) {
                        $translation = new ZNF_Presentation_Translation('ZNF', $_SESSION['znf']['lang']);
                        throw new ZNF_Action_RequestProcessorException($translation->get('errorLoadView'));
                    }

                    require_once($input);
                } else {
                    // forward is set to an action
                    $this->process($input);
                }
                return false;
            }

        }

        return true;
    }

    /**
     * If the <i>forward</i> attribute for the current action is found calls <i>ZNF_Action_RequestProcessor->process()</i> with the forward found and break the current proccess flow.
     *
     * @access protected
     * @param ZNF_Action_ActionMapping $mapping
     * @return boolean
     */
    protected function _processForward($mapping)
    {
        $forward = $mapping->getForward();

        if (!$forward) {
            // forward not set
            return false;
        }

        if (ereg('//', $forward)) {
            // forward is set to an action of another module

            // extract module and action name
            $moduleAction = explode('//', $forward);

            $znf = new ZNF();

            // update module and action in the request
            $znf->updateModule($moduleAction[0]);
            $znf->updateAction($moduleAction[1]);

            $znf->process();

        } else if (ereg('/', $forward)) {
            // forward is set to a view

            $forward = ZNF::MODULES_DIR . '/' . $forward;

            if (!file_exists($forward)) {
                $translation = new ZNF_Presentation_Translation('ZNF', $_SESSION['znf']['lang']);
                throw new ZNF_Action_RequestProcessorException($translation->get('errorLoadView'));
            }

            require_once($forward);
        } else {
            // forward is set to an action
            $this->process($forward);
        }

        return true;
    }

    /**
     * Finds the <i>ZNF_Action_Action</i> class to handle the action.
     *
     * Constructs an instance and returns it to the
     * <i>ZNF_Action_RequestProcessor->process()</i> method.
     *
     * @access protected
     * @param ZNF_Action_ActionMapping $mapping
     * @return ZNF_Action_Action
     */
    protected function _processActionCreate($mapping)
    {
        $type = $mapping->getType();
        $file = ZNF::MODULES_DIR . '/' . preg_replace('/_/', '/', $type) . '.php';

        if (!file_exists($file)) {
            $translation = new ZNF_Presentation_Translation('ZNF', $_SESSION['znf']['lang']);
            throw new ZNF_Action_RequestProcessorException($translation->get('errorLoadAction'));
        }

        // requires the file of the controller of the action
        require_once($file);

        return new $type();
    }

    /**
     * Calls the <i>ZNF_Action_Action->execute()</i> method and returns the result to the <i>ZNF_Action_RequestProcessor->process()</i> method.
     *
     * @access protected
     * @param ZNF_Action_Action $action
     * @param ZNF_Action_ActionForm $form
     * @param ZNF_Action_ActionMapping $mapping
     * @return ZNF_Action_ActionForward
     */
    protected function _processActionPerform($action, $form, $mapping)
    {
        return $action->execute($form, $mapping);
    }

    /**
     * Forwards or redirects to the specified destination, by the specified mechanism.
     *
     * @access protected
     * @param ZNF_Action_ActionForward $forward
     * @param ZNF_Action_ActionForm $form
     * @param ZNF_Action_ActionMapping $mapping
     * @return boolean
     */
    protected function _processForwardConfig($forward, $form, $mapping)
    {
        if (!$forward) {
            // forward not set
            return false;
        }

        $path = $forward->getPath();

        if (!$path) {
            // path not set
            return false;
        }

        $pathArray = null;

        // check if the forward refer to an action of another module
        if (ereg('//', $path)) {
            $pathArray = explode('//', $path);
        }

        $redirect = $forward->getRedirect();

        if ($redirect == 'true') {

            // checks if https was used
            if (isset($_SERVER['HTTPS'])) {
                $location = 'https://';
            } else {
                $location = 'http://';
            }

            $location .= "{$_SERVER['SERVER_NAME']}";

            if ($_SERVER['SERVER_PORT'] != '80' && $_SERVER['SERVER_PORT'] != '443') {
                $location .= ":{$_SERVER['SERVER_PORT']}";
            }

            $location .= $_SERVER['SCRIPT_NAME'];

            // makes the browser reloads the new url
            if ($pathArray) {
                header('Location: ' . "{$location}?" . ZNF::MODULE_INDEX . "={$pathArray[0]}" . "&amp;" . ZNF::ACTION_INDEX . "={$pathArray[1]}");
            } else {
                header('Location: ' . "{$location}?" . ZNF::ACTION_INDEX . "={$path}");
            }

        } elseif (ereg('//', $path)) {
            // forward is set to an action of another module

            $znf = new ZNF();

            // update module and action in the request
            $znf->updateModule($pathArray[0]);
            $znf->updateAction($pathArray[1]);

            $znf->process();

        } elseif (ereg('/', $path)) {
            // forwards to a view

            $path = ZNF::MODULES_DIR . '/' . $path;

            if (!file_exists($path)) {
                $translation = new ZNF_Presentation_Translation('ZNF', $_SESSION['znf']['lang']);
                throw new ZNF_Action_RequestProcessorException($translation->get('errorLoadView'));
            }

            require_once($path);
        } else {
            // forwards to an action
            $this->process($path);
        }

        return true;
    }

    /**
     * Handles the exception.
     *
     * @access protected
     * @param Exception $e
     * @param ZNF_Action_ActionForm $form
     * @param ZNF_Action_ActionMapping $mapping
     * @return boolean
     */
    protected function _processException($e, $form, $mapping)
    {
        $e->getTraceAsString();
        return true;
    }

    /**
     * Destroys the <i>ZNF_Action_RequestProcessor</i> object.
     *
     * @access public
     */
    public function __destruct()
    {
    }

}

/**
 * <i>ZNF_Action_RequestProcessorException</i> is the exception type for the <i>ZNF_Action_RequestProcessor</i> class.
 *
 * <i>ZNF_Action_RequestProcessorException</i> extends the <i>Exception</i> class of PHP5.
 *
 * @access     public
 * @package    ZNF
 * @subpackage Action
 * @author     Alessandro Rossini <http://www.alessandrorossini.org>
 * @author     Graziano Liberati <http://www.liberati.org>
 * @copyright  2004-2007 The ZNF Development Team
 * @license    LGPL License 2.1 <http://www.gnu.org/copyleft/lesser.html>
 * @version    SVN $Id: RequestProcessor.php 44 2007-07-22 23:55:30Z evolic $
 * @since      Release 0.5.0
 * @link       http://www.zeronotice.org
 */
class ZNF_Action_RequestProcessorException extends Exception
{
}

?>
